// ==========================================================
// Input/Output functions
//
// Design and implementation by
// - Floris van den Berg (flvdberg@wxs.nl)
//
// This file is part of FreeImage 3
//
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
// THIS DISCLAIMER.
//
// Use at your own risk!
// ==========================================================

#include "FreeImage.h"
#include "Utilities.h"
#include "FreeImageIO.h"

// =====================================================================
// File IO functions
// =====================================================================

unsigned DLL_CALLCONV
_ReadProc(void *buffer, unsigned size, unsigned count, fi_handle handle) {
    return (unsigned)fread(buffer, size, count, (FILE *)handle);
}

unsigned DLL_CALLCONV
_WriteProc(void *buffer, unsigned size, unsigned count, fi_handle handle) {
    return (unsigned)fwrite(buffer, size, count, (FILE *)handle);
}

int DLL_CALLCONV
_SeekProc(fi_handle handle, long offset, int origin) {
    return fseek((FILE *)handle, offset, origin);
}

long DLL_CALLCONV
_TellProc(fi_handle handle) {
    return ftell((FILE *)handle);
}

// ----------------------------------------------------------

void
SetDefaultIO(FreeImageIO *io) {
    io->read_proc  = _ReadProc;
    io->seek_proc  = _SeekProc;
    io->tell_proc  = _TellProc;
    io->write_proc = _WriteProc;
}

// =====================================================================
// Memory IO functions
// =====================================================================

unsigned DLL_CALLCONV
_MemoryReadProc(void *buffer, unsigned size, unsigned count, fi_handle handle) {
    unsigned x;

    FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(((FIMEMORY*)handle)->data);

    for(x = 0; x < count; x++) {
        long remaining_bytes = mem_header->file_length - mem_header->current_position;
        //if there isn't size bytes left to read, set pos to eof and return a short count
        if( remaining_bytes < (long)size ) {
            if(remaining_bytes > 0) {
                memcpy( buffer, (char *)mem_header->data + mem_header->current_position, remaining_bytes );
            }
            mem_header->current_position = mem_header->file_length;
            break;
        }
        //copy size bytes count times
        memcpy( buffer, (char *)mem_header->data + mem_header->current_position, size );
        mem_header->current_position += size;
        buffer = (char *)buffer + size;
    }
    return x;
}

unsigned DLL_CALLCONV
_MemoryWriteProc(void *buffer, unsigned size, unsigned count, fi_handle handle) {
    void *newdata;
    long newdatalen;

    FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(((FIMEMORY*)handle)->data);

    //double the data block size if we need to
    while( (mem_header->current_position + (long)(size * count)) >= mem_header->data_length ) {
        //if we are at or above 1G, we cant double without going negative
        if( mem_header->data_length & 0x40000000 ) {
            //max 2G
            if( mem_header->data_length == 0x7FFFFFFF ) {
                return 0;
            }
            newdatalen = 0x7FFFFFFF;
        } else if( mem_header->data_length == 0 ) {
            //default to 4K if nothing yet
            newdatalen = 4096;
        } else {
            //double size
            newdatalen = mem_header->data_length << 1;
        }
        newdata = realloc( mem_header->data, newdatalen );
        if( !newdata ) {
            return 0;
        }
        mem_header->data = newdata;
        mem_header->data_length = newdatalen;
    }
    memcpy( (char *)mem_header->data + mem_header->current_position, buffer, size * count );
    mem_header->current_position += size * count;
    if( mem_header->current_position > mem_header->file_length ) {
        mem_header->file_length = mem_header->current_position;
    }
    return count;
}

int DLL_CALLCONV
_MemorySeekProc(fi_handle handle, long offset, int origin) {
    FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(((FIMEMORY*)handle)->data);

    // you can use _MemorySeekProc to reposition the pointer anywhere in a file
    // the pointer can also be positioned beyond the end of the file

    switch(origin) { //0 to filelen-1 are 'inside' the file
        default:
        case SEEK_SET: //can fseek() to 0-7FFFFFFF always
            if( offset >= 0 ) {
                mem_header->current_position = offset;
                return 0;
            }
            break;

        case SEEK_CUR:
            if( mem_header->current_position + offset >= 0 ) {
                mem_header->current_position += offset;
                return 0;
            }
            break;

        case SEEK_END:
            if( mem_header->file_length + offset >= 0 ) {
                mem_header->current_position = mem_header->file_length + offset;
                return 0;
            }
            break;
    }

    return -1;
}

long DLL_CALLCONV
_MemoryTellProc(fi_handle handle) {
    FIMEMORYHEADER *mem_header = (FIMEMORYHEADER*)(((FIMEMORY*)handle)->data);

    return mem_header->current_position;
}

// ----------------------------------------------------------

void
SetMemoryIO(FreeImageIO *io) {
    io->read_proc  = _MemoryReadProc;
    io->seek_proc  = _MemorySeekProc;
    io->tell_proc  = _MemoryTellProc;
    io->write_proc = _MemoryWriteProc;
}
